package rlpw.swing;

import java.awt.Color;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;

import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import rlpw.swing.table.ColorEditor;
import rlpw.swing.table.ColorRenderer;
import rlpw.swing.table.Column;
import rlpw.swing.table.DateCellRenderer;

/**
 *
 * @param <T>
 * @author weinpau
 */
public class JOTable<T> extends JTable implements View<T> {

    private ReflectionDataList refs;
    private AbstractTableModel atm;
    private Model<T> model;
    private Comparator<T> sorter;
    private Filter<T> filter;

    public JOTable(Class<T> c) {
        this(c, new DefaultModel<T>());
    }

    public JOTable(Class<T> c, Model<T> m) {

        refs = new ReflectionDataList(c);

        setDataModel(m);
        setFilter(null);
        model.addView(this);

        atm = new AbstractTableModel() {

            @Override
            public boolean isCellEditable(int row, int column) {
                return refs.get(column).isEditable();
            }

            public int getRowCount() {
                return new FilteredList<T>(JOTable.this).size();
            }

            @Override
            public int getColumnCount() {
                return refs.size();
            }

            @Override
            public Object getValueAt(int rowIndex, int columnIndex) {
                try {
                    return refs.get(columnIndex).get(new FilteredList<T>(JOTable.this).get(rowIndex));
                } catch (Exception ex) {
                    return null;
                }
            }

            @Override
            public void setValueAt(Object aValue, int rowIndex, int columnIndex) {

                rowIndex = new FilteredList<T>(JOTable.this).toDataModelIndex(rowIndex);

                ReflectionData ref = refs.get(columnIndex);
                T obj = getDataModel().get(rowIndex);

                try {
                    ref.set(obj, aValue);
                    try {
                        fireTableRowsUpdated(rowIndex, rowIndex);
                        getDataModel().updateViews();
                    } catch (Exception fireupdate) {
                    }
                } catch (Exception ex) {
                    throw new IllegalArgumentException("Can't set this attribute");
                }
            }

            @Override
            public String getColumnName(int column) {
                return refs.get(column).getAnnotation().value();
            }

            @Override
            public Class getColumnClass(int c) {
                try {
                    return getValueAt(0, c).getClass();
                } catch (Exception excep) {
                    return refs.get(c).getType();
                }
            }
        };

        setModel(atm);

        getDataModel().addListDataListener(new ListDataListener() {

            public void intervalAdded(ListDataEvent e) {
                FilteredList<T> fl = new FilteredList<T>(JOTable.this);
                atm.fireTableRowsInserted(fl.toViewIndex(e.getIndex0()), fl.toViewIndex(e.getIndex1()));
            }

            public void intervalRemoved(ListDataEvent e) {
                FilteredList<T> fl = new FilteredList<T>(JOTable.this);
                atm.fireTableRowsDeleted(fl.toViewIndex(e.getIndex0()), fl.toViewIndex(e.getIndex1()));
            }

            public void contentsChanged(ListDataEvent e) {
                FilteredList<T> fl = new FilteredList<T>(JOTable.this);
                atm.fireTableRowsUpdated(fl.toViewIndex(e.getIndex0()), fl.toViewIndex(e.getIndex1()));
            }
        });


        for (ReflectionData ref : refs) {

            Column cn = ref.getAnnotation();

            TableColumn tc = getColumnModel().getColumn(refs.indexOf(ref));

            if (cn.width() > -1) {
                tc.setWidth(cn.width());
                tc.setPreferredWidth(cn.width());
            }
            if (!cn.resizable()) {
                tc.setMinWidth(tc.getWidth());
                tc.setMaxWidth(tc.getWidth());
            }
        }

        // init column editors
        setDefaultEditor(Date.class, "net.sf.nachocalendar.table.DateFieldTableEditor");
        setDefaultEditor(Date.class, "com.toedter.calendar.JDateChooserCellEditor");
        setDefaultEditor(Date.class, "org.jdesktop.swingx.table.DatePickerCellEditor");
        setDefaultEditor(Color.class, new ColorEditor());
        // init column renderers
        setDefaultRenderer(Date.class, new DateCellRenderer());
        setDefaultRenderer(Color.class, new ColorRenderer());
        setDefaultRenderer(Boolean.class, new rlpw.swing.table.BooleanRenderer());

        RowSorter<TableModel> rowsorter = new TableRowSorter<TableModel>(atm);

        setRowSorter(rowsorter);
    }

    public void setDefaultEditor(Class<?> c, String cn) {
        try {
            setDefaultEditor(c, (TableCellEditor) Class.forName(cn).newInstance());
        } catch (Exception excep) {
        }
    }

    public void setDefaultRenderer(Class<?> c, String cn) {
        try {
            setDefaultRenderer(c, (TableCellRenderer) Class.forName(cn).newInstance());
        } catch (Exception excep) {
        }
    }

    public void updateView() {
        revalidate();
        repaint();
    }

    public Model<T> getDataModel() {
        return model;
    }

    public void setDataModel(Model<T> model) {
        this.model = model;
        updateView();
    }

    public CheckModel getCheckModel() {
        return null;
    }

    public SelectModel<T> getSelectModel() {
        return new ListSelectModel<T>(getDataModel(), getSelectionModel(), this);
    }

    public Filter<T> getFilter() {
        return filter;
    }

    public void setFilter(Filter<T> filter) {
        if (filter == null) {
            this.filter = new DefaultFilter<T>();
        } else {
            this.filter = filter;
        }

        updateView();
    }

    public void setSorter(Comparator<T> comp) {
        sorter = comp;
        updateView();
    }

    public Comparator<T> getSorter() {
        return sorter;
    }
}

class ReflectionDataList extends ArrayList<ReflectionData> {

    public ReflectionDataList(Class<?> c) {

        // reflection scanner

        for (Field f : c.getDeclaredFields()) {
            if (f.isAnnotationPresent(Column.class)) {
                f.setAccessible(true);

                Column cn = f.getAnnotation(Column.class);
                add(new FieldData(f, cn));

            }
        }

        for (Method m : c.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Column.class)) {
                if (m.getParameterTypes().length > 0
                        || m.getReturnType() == void.class) {
                    continue;
                }
                m.setAccessible(true);

                Column cn = m.getAnnotation(Column.class);
                add(new MethodData(m, cn));

            }
        }
    }

    @Override
    public boolean add(ReflectionData rd) {

        String before = rd.getAnnotation().beforecolumn();

        if (!before.isEmpty()) {
            try {
                for (ReflectionData r : this) {

                    if (before.equals(r.getAnnotation().value())) {
                        add(indexOf(r), rd);
                        return true;
                    }
                }
            } catch (Exception exceo) {
            }
        }

        return super.add(rd);
    }
}

abstract class ReflectionData {

    private Column annotation;

    public ReflectionData(Column annotation) {
        this.annotation = annotation;
    }

    public Column getAnnotation() {
        return annotation;
    }

    abstract boolean isEditable();

    abstract Class<?> getType();

    abstract Object get(Object obj) throws Exception;

    abstract void set(Object obj1, Object obj2) throws Exception;
}

class MethodData extends ReflectionData {

    private Method method;

    public MethodData(Method method, Column anno) {
        super(anno);
        this.method = method;

    }

    @Override
    Object get(Object obj) throws Exception {
        return method.invoke(obj);
    }

    @Override
    void set(Object obj1, Object obj2) throws Exception {
        throw new UnsupportedOperationException("Not supported.");
    }

    @Override
    Class<?> getType() {
        return method.getReturnType();
    }

    @Override
    boolean isEditable() {
        return false;
    }
}

class FieldData extends ReflectionData {

    private Field field;

    public FieldData(Field field, Column anno) {
        super(anno);
        this.field = field;
    }

    @Override
    Object get(Object obj) throws Exception {
        return field.get(obj);
    }

    @Override
    void set(Object obj, Object value) throws Exception {
        field.set(obj, value);
    }

    @Override
    Class<?> getType() {
        return field.getType();
    }

    @Override
    boolean isEditable() {
        return getAnnotation().editable();
    }
}
